﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;

namespace Common.Util.Validation
{
    /// <summary>
    /// Implements XML Validation
    /// </summary>
    public class XMLValidator : IDisposable
    {
        // static readonly Logger Log = LogManager.GetCurrentClassLogger();

        #region private
        /// <summary>
        /// Indicates if the current xml file is valid or not
        /// </summary>
        private bool _isValid;
        /// <summary>
        /// Stores the resultant error messages. 
        /// </summary>
        private StringBuilder _errorMessages;

        /// <summary>
        /// Schema to validate
        /// </summary>
        XmlSchema _schema;

        XmlTextReader _xmlTextReader;

        #endregion

        #region properties
        /// <summary>
        /// Indicates if the current document is valid
        /// </summary>
        public bool IsValid
        {
            get { return _isValid; }
            set { _isValid = value; }
        }

        /// <summary>
        /// Contain the errors messages 
        /// </summary>
        public String ErrorMessages
        {
            get { return _errorMessages.ToString(); }
            set { _errorMessages.Append("\n\r").Append(value); }
        }
        /// <summary>
        /// returns the XMLTextReader 
        /// </summary>
        public XmlTextReader XmlTextReader
        {
            get { return _xmlTextReader; }
            private set { _xmlTextReader = value; }
        }
        /// <summary>
        /// Accessor for the current processing schema
        /// </summary>
        public XmlSchema Schema
        {
            get { return _schema; }
            private set { _schema = value; }
        }
        #endregion

        #region Constructors
        ///<summary>
        ///</summary>
        public XMLValidator()
        {
            Reset();
        }
        #endregion


        ///<summary>
        ///</summary>
        ///<param name="xmlFileName"></param>
        ///<param name="schemaFileName"></param>
        public XMLValidator(String xmlFileName, String schemaFileName)
        {

            this.ValidateXML(xmlFileName, schemaFileName);

        }

        #region Public

        /// <summary>
        /// Validates the schema file. This function handles  two different aspects  of checking schema's content
        ///  1- Valid XML.
        ///  2- Acceptable XSD format.  
        /// </summary>
        /// <param name="xsdFileName"></param>
        /// <returns></returns>
        public bool ValidateSchema(String xsdFileName)
        {
            Reset();
            try
            {
                using (XmlTextReader = new XmlTextReader(xsdFileName))
                {
                    var schemaSet = new XmlSchemaSet();
                    schemaSet.ValidationEventHandler += this.ValidationHandler;


                    Schema = XmlSchema.Read(XmlTextReader, this.ValidationHandler);

                    if (IsValid)
                    {
                        schemaSet.Add(Schema);
                        schemaSet.Compile();
                    }
                }
            }
            catch (FormatException formatException)
            {
                String.Format("FormatException: {0} \n\r Trace: \n\r{0}", formatException.Message, formatException.StackTrace);
                IsValid = false;
                // Log.WriteException(formatException, "Error Validating Schema");
            }
            catch (Exception e)
            {
                // Log.WriteException(e, "Error Validating Schema");
                ErrorMessages = e.Message;
                IsValid = false;
            }

            return IsValid;
        }

        /// <summary>
        /// Validates the schema, and validates the XML.
        /// </summary>
        /// <param name="xmlFileName"></param>
        /// <param name="schemaFileName"></param>
        /// <returns></returns>
        public bool ValidateXML(String xmlFileName, String schemaFileName)
        {
            if (ValidateSchema(schemaFileName))
            {
                try
                {
                    XmlTextReader = new XmlTextReader(xmlFileName);

                    var readerSettings = new XmlReaderSettings();
                    readerSettings.ValidationType = ValidationType.Schema;
                    readerSettings.ValidationEventHandler += this.ValidationHandler;
                    readerSettings.Schemas.Add(Schema);

                    XmlReader reader = XmlReader.Create(XmlTextReader, readerSettings);
                    while (reader.Read()) { };

                }
                catch (FormatException formatException)
                {
                    String.Format("FormatException: {0} \n\r Trace: \n\r{0}", formatException.Message, formatException.StackTrace);
                }
                catch (Exception e)
                {
                    ErrorMessages = e.Message;
                    _isValid = false;
                }
            }

            return _isValid;
        }

        /// <summary>
        /// This function validates the xml using the schema defined in the 
        /// xsi:schemaLocation. If the xsi:schemaLocation is not present or the path is invalid,
        /// the xml is not valid.
        /// </summary>
        public bool ValidateXML(String xmlFileName)
        {
            Reset();
            try
            {
                String schemaFileName = "";
                using (var xmlFileReader = new XmlTextReader(xmlFileName))
                {
                    var xmlDocument = new XmlDocument();

                    xmlDocument.Load(xmlFileReader);

                    XPathNavigator nav = xmlDocument.CreateNavigator();

                    // Select all extension types.
                    XPathExpression expressions = nav.Compile("/");

                    // Create and set namespace resolution context.
                    var nsmgr = new XmlNamespaceManager(nav.NameTable);
                    nsmgr.AddNamespace("xsi", XmlSchema.Namespace);

                    expressions.SetContext(nsmgr);

                    // Select all extension types.
                    XPathNodeIterator pathIterator = nav.Select(expressions);
                    pathIterator.MoveNext();
                    if (pathIterator.Count == 0)
                    {
                        new Exception("xsi:schemalocation not present in {0}" + xmlFileName);
                    }

                    XmlAttribute attribute = ((IHasXmlNode)pathIterator.Current).GetNode().LastChild.Attributes["xsi:schemaLocation"];
                    if (attribute == null)
                    {
                        new Exception("xsi:schemalocation not present in {0}" + xmlFileName);
                    }
                    if (attribute != null) schemaFileName = attribute.Value;
                }

                ValidateXML(xmlFileName, schemaFileName);
            }
            catch (Exception e)
            {
                ErrorMessages = e.Message;
                _isValid = false;
            }
            return _isValid;
        }

        #endregion

        #region Private

        private void Reset()
        {
            Schema = new XmlSchema();
            _errorMessages = new StringBuilder();
            _isValid = true;

        }
        #endregion

        #region ValidationHandler

        ///<summary>
        ///</summary>
        ///<param name="sender"></param>
        ///<param name="args"></param>
        public void ValidationHandler(object sender, ValidationEventArgs args)
        {
            _isValid = false;
            ErrorMessages = String.Format("Severity: {0} ({4})\n\rLine    : {2}  Position: {3} \n\rMessage : {1} ", args.Severity.ToString(), args.Message, args.Exception.LineNumber, args.Exception.LinePosition, args.Exception.SourceUri);
        }

        #endregion

        #region IDisposable Members
        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            try
            {

                if (XmlTextReader != null)
                {
                    XmlTextReader.Close();
                }
            }
            catch //(Exception e)
            {
                //Log.WriteException(e, "Error disposing XmlTextReader");
            }
        }

        #endregion
    }
}
